/**
 * Плагин для сосздание элемента ввода с гео-подсказкой
 * @author Vladimir Mozolev (mutantdd@yandex.ru)
 */
(function($, google){

    function getSimpleAdressFormat(place) {
        var data ={};
        $.each(place.address_components, function(index, object){
            var name = object.types[0];
            data[name] = object.long_name;
            data[name + "_short"] = object.short_name;
        });
        return data;
    }

    function addressFormat(place) {
        var priority = ['street_number', 'route', 'sublocality', 'locality', 
                        'administrative_area_level_3', 'administrative_area_level_1',
                    'country', 'postal_code'];
        priority.reverse();
        var undesirableElements = ['administrative_area_level_3', 'country', 'postal_code', 'sublocality'];

        var simplePlace = getSimpleAdressFormat(place);
        var result = "";
        for (var i in priority) {
            if ($.inArray(priority[i], undesirableElements) < 0) {
                if (simplePlace[priority[i]]) {
                    if (result) {
                        if (priority[i] == 'street_number') {
                            result += " дом ";
                        } else {
                            result += ", ";
                        }
                    }
                    result += simplePlace[priority[i]];
                }
            }
        }
        return result;
    }

    function getCountry(place) {
        var simplePlace = getSimpleAdressFormat(place);
        return simplePlace.country_short;
    }
    
    function sendRequestGeoCoding(request, async, callBackResult) {
        $.ajax({
            url: options.serviceUrl,
            dataType: "json",
            data: request,
            type: "GET",
            async: async,
            success: function (responce) {

                callBackResult(responce.results, responce.status);
            }
        });
    }


  var GeoInput = function (element, options) {
    this.$element = $(element)
    this.options = $.extend({}, $.fn.geoinput.defaults, options)
    this.matcher = this.options.matcher || this.matcher
    this.sorter = this.options.sorter || this.sorter
    this.highlighter = this.options.highlighter || this.highlighter
    this.updater = this.options.updater || this.updater
    this.$menu = $(this.options.menu)
    this.shown = false
    this.places = [];
    this.listen()
  }
  

  GeoInput.prototype = {

    constructor: GeoInput

    
      , getPlaces: function() {
          return this.places;
      }
      , setPlaces: function(places) {
          this.places = places;
      }
      , setValue: function(value) {
          this.$element.val(value);
          this.lookup(null, true);
          this.select();
      }
    ,source: function (query, process, notAsync) {
        var request = {'address': query, 'region': this.options.country};
        this.places = [];
        
        var that = this;
        
        sendRequestGeoCoding(request, notAsync ? false : true, function(results, status) {
            that.hide();
            if (status === google.maps.GeocoderStatus.OK) {
                var data = new Array();
                $.each(results, function(i, place) {
                    if (getCountry(place) == that.options.country.toUpperCase()) {
                        place.string = addressFormat(place);
                        data.push(place);
                    }
                });
                console.log("found locations:");
                console.log(data);
                that.places = data;
                process(data);
                if (data.length != 0) {
                    if (that.options.findPlace) {
                        that.options.findPlace(data);
                    }
                }
            } else {
                throw "Error geocode status " + status;
            }
        });


    }
  , select: function () {
      var val = this.$menu.find('.active').attr('data-value')
      if (this.places.length != 0){
          this.$element
            .val(this.updater(this.places[val]))
            .change();
          this.places = [this.places[val]];
      }
      return this.hide()
    }

  , updater: function (item) {
      return item.string
    }

  , show: function () {
      var pos = $.extend({}, this.$element.position(), {
        height: this.$element[0].offsetHeight
      })

      this.$menu
        .insertAfter(this.$element)
        .css({
          top: pos.top + pos.height
        , left: pos.left
        })
        .show()

      this.shown = true
      return this
    }

  , hide: function () {
      this.$menu.hide()
      this.shown = false
      return this
    }

  , lookup: function (event, notAsync) {
      var items

      this.query = this.$element.val()

      if (!this.query || this.query.length < this.options.minLength) {
        return this.shown ? this.hide() : this
      }

      items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this), notAsync) : this.source

      return items ? this.process(items) : this
    }

  , process: function (items) {
      
      var that = this

      items = $.grep(items, function (item) {
        return that.matcher(item)
      })

      items = this.sorter(items)

      if (!items.length) {
        return this.shown ? this.hide() : this
      }

      return this.render(items.slice(0, this.options.items)).show()
    }

    ,matcher: function() {
        return true;
    }
,
sorter: function (items) {
    var beginswith = []
        , caseSensitive = []
        , caseInsensitive = []
        , item

    while (item = items.shift()) {
        var strItem = item.string;
        if (!strItem.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
        else if (~strItem.indexOf(this.query)) caseSensitive.push(item)
        else caseInsensitive.push(item)
    }

    return beginswith.concat(caseSensitive, caseInsensitive)
    }
  , highlighter: function (item) {
      var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
      return item.string.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
        return '<strong>' + match + '</strong>'
      })
    }

  , render: function (items) {
      var that = this

      items = $(items).map(function (i, item) {
        i = $(that.options.item).attr('data-value', i)
        i.find('a').html(that.highlighter(item))
        return i[0]
      })

      items.first().addClass('active')
      this.$menu.html(items)
      return this
    }

  , next: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , next = active.next()

      if (!next.length) {
        next = $(this.$menu.find('li')[0])
      }

      next.addClass('active')
    }

  , prev: function (event) {
      var active = this.$menu.find('.active').removeClass('active')
        , prev = active.prev()

      if (!prev.length) {
        prev = this.$menu.find('li').last()
      }

      prev.addClass('active')
    }

  , listen: function () {
      this.$element
        .on('focus',    $.proxy(this.focus, this))
        .on('blur',     $.proxy(this.blur, this))
        .on('keypress', $.proxy(this.keypress, this))
        .on('keyup',    $.proxy(this.keyup, this))

      if (this.eventSupported('keydown')) {
        this.$element.on('keydown', $.proxy(this.keydown, this))
      }

      this.$menu
        .on('click', $.proxy(this.click, this))
        .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
        .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
    }

  , eventSupported: function(eventName) {
      var isSupported = eventName in this.$element
      if (!isSupported) {
        this.$element.setAttribute(eventName, 'return;')
        isSupported = typeof this.$element[eventName] === 'function'
      }
      return isSupported
    }

  , move: function (e) {
      if (!this.shown) return

      switch(e.keyCode) {
        case 9: // tab
        case 27: // escape
          e.preventDefault()
          break
        case 13: // enter
          e.preventDefault()
          this.select()
          break;
        case 38: // up arrow
          e.preventDefault()
          this.prev()
          break

        case 40: // down arrow
          e.preventDefault()
          this.next()
          break
      }

      e.stopPropagation()
    }

  , keydown: function (e) {
      this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
      this.move(e)
    }

  , keypress: function (e) {
      if (this.suppressKeyPressRepeat) return
      this.move(e)
    }

  , keyup: function (e) {
      switch(e.keyCode) {
        case 40: // down arrow
        case 38: // up arrow
        case 16: // shift
        case 17: // ctrl
        case 18: // alt
          break

        case 9: // tab
        case 13: // enter
          if (!this.shown) return
          if (this.options.insertValue) {
              this.select();
          } else {
              this.hide();
          }
          break

        case 27: // escape
          if (!this.shown) return
          this.hide()
          break

        default:
          this.lookup()
      }

      e.stopPropagation()
      e.preventDefault()
  }

  , focus: function (e) {
      this.focused = true
    }

  , blur: function (e) {
      this.focused = false
      if (!this.mousedover && this.shown) this.hide()
    }

  , click: function (e) {
      e.stopPropagation()
      e.preventDefault()
      this.select()
      this.$element.focus()
    }

  , mouseenter: function (e) {
      this.mousedover = true
      this.$menu.find('.active').removeClass('active')
      $(e.currentTarget).addClass('active')
    }

  , mouseleave: function (e) {
      this.mousedover = false
      if (!this.focused && this.shown) this.hide()
    }

  }




  $.fn.geoinput = function (option, argument) {
    if (typeof option == 'string') {
        var $this = $(this[0])
        var data = $this.data('geoinput');
        return data[option](argument);
    }
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('geoinput')
        , options = typeof option == 'object' && option
      if (!data) $this.data('geoinput', (data = new GeoInput(this, options)))
    })
  }

  $.fn.geoinput.defaults = {
    items: 15
  , menu: '<ul class="typeahead dropdown-menu"></ul>'
  , item: '<li><a href="#"></a></li>'
  , minLength: 0
  , insertValue: false
  , country: "ua"
  }

  $.fn.geoinput.Constructor = GeoInput

// Загрузка глобальных настроек
    var options = {
        serviceUrl: null
    }

    $.geoinput = function (inOptions) {
        $.extend(options, inOptions);
    }

}(jQuery, google));